12.2.1 ViewGroup 绘制流程
View 和 ViewGroup 的绘制流程基本相同,只是在 ViewGroup 中不仅要绘制自己,还要绘制其中的子控件,而 View 只需要绘制自己就可以了。
绘制流程分为三步:测量、布局、绘制,分别对应 onMeasure()、onLayout()、onDraw() 函数。
- onMeasure():测量当前控件的大小,为正式布局提供建议(注意:只是建议,至于用不用,要看 onLayout() 函数)。
- onLayout():使用 layout() 函数对所有字控件进行布局。
- onDraw():根据布局的位置绘图。
12.2.2 onMeasure() 函数与 MeasureSpec
布局绘画涉及两个过程:测量过程和布局过程。测量过程通过 measure() 函数来实现,是 View 树自顶向下的遍历,每个 View 在循环过程中将尺寸细节往下传递,当测量过程完成以后,所有的 View 都存储了自己的尺寸。布局过程则通过 layout() 函数来实现,也是自顶向下的,在这个过程中,每个父 View 负责通过计算好的尺寸放置它的子 View。
onMeasure() 函数是用来测量当前控件大小的,给 onLayout() 函数提供数值参考。需要特别注意的是,测量完成以后,要通过 setMeasuredDimension(int, int) 函数设置给系统。
1. onMeasure() 函数
|
|
参数 widthMeasureSpec 和 heightMeasureSpec 是父类传递给当前 View 的一个建议值,即想把当前 View 的尺寸设置为宽 widthMeasureSpec、高 heightMeasureSpec。
2. MeasureSpec 的组成
widthMeasureSpec 和 heightMeasureSpec 转换为二进制数字表示,它们都是 32 位的,前 2 位代表模式(mode),后面 30 位代表数值(size)。
1)模式分类
模式 | 二进制值 | 含义 | 对应 XML |
---|---|---|---|
UNSPECIFIED | 00000000…00000000 | 父元素不对子元素的确切大小,子元素可以得到任意想要的大小 | 不常用 |
EXACTLY | 01000000…00000000 | 父元素决定子元素的确切大小,子元素将被限定在给定的边界里而忽略它本身的大小 | match_parent、具体数值 |
AT_MOST | 10000000…00000000 | 子元素至多达到指定大小的值 | wrap_content |
2)模式提取
使用 & 位运算。
3)MeasureSpec
Android 已经为我们提供了 MeasureSpec 类来实现模式和数值的提取。
另外,模式的取值为:
实际运用:
12.2.3 onLayout() 函数
onLayout() 是实现所有子控件布局的函数。那关于它自己的布局怎么办呢?是在父控件中由它的父控件完成的。就这样一层一层地向上由各自的父控件完成对自己的布局,直到所有控件的顶层节点。在所有的控件的顶部有一个 ViewRoot,它才是所有控件的祖先节点。
ViewRoot 使用 setFrame(l, t, r, b) 函数中设置自己的位置,设置结束以后才会调用 onLayout(changed, l, t, r, b) 函数来设置内部所有子控件的位置。
示例:
|
|
res/layout/act_main.xml
注意:getMeasuredWidth() 与 getWidth() 获得的值大部分时候是相同的,但含义却是根本不一样的。前者是在 measure() 过程结束后就可以获取到宽度值,而后者是要在 layout() 过程结束后才能获取到宽度值;前者的值是通过 setMeasuredDimension() 函数来进行设置的,而后者的值是通过 layout(left, top, right, bottom) 函数来进行设置的。
12.2.4 获取子控件 margin 值的方法
1. 获取方法及示例
在上面 MyLinLayout 例子的基础上,添加 layout_margin 参数。
重写 generateLayoutParams() 和 generateDefaultLayoutParams(),返回对应的 MarginLayoutParams() 函数的实例。
重写 onMeasure() 和 onLayout() 函数,修正获取子控件的宽高逻辑。
最终效果如下图所示。
完整代码:
2. 原理
在 container 中初始化子控件时,会调用 LayoutParams generateLayoutParams(LayoutParams p) 函数来为子控件生成对应的布局属性,但默认只生成 layout_width 和 layout_height 所对应的布局参数,即在正常情况下调用 generateLayoutParams() 函数生成的 LayoutParams 实例是不能获取到 margin 值的。所以,如我我们还需要与 margin 相关的参数,就只能重写 generateLayoutParams() 函数,返回派生自 LayoutParams 的子类 MarginLayoutParams,根据类的多态性,可以直接将其强转成 MarginLayoutParams 实例。为了安全起见,也可以利用 instanceof 来进行判断。